package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

// 定义结构体匹配JSON数据
type ScenicSpot struct {
	ID          string      `json:"id"`
	Name        string      `json:"name"`
	Area        string      `json:"area"`
	Price       string      `json:"price"`
	FreeTickets string      `json:"freeTickets"`
	ServiceTime string      `json:"serviceTime"`
	Link        string      `json:"link"`
	Detail      *DetailInfo `json:"detail,omitempty"`
	Location    string      `json:"location,omitempty"`
}

// 详细信息结构体
type DetailInfo struct {
// 	CardRights string `json:"card_rights,omitempty"`
// 	Ticket     string `json:"ticket,omitempty"`
// 	Status     string `json:"status,omitempty"`
	OpenTime   string `json:"open_time,omitempty"`
	Phone      string `json:"phone,omitempty"`
	Address    string `json:"address,omitempty"`
}

// 高德地图API响应结构
type AmapResponse struct {
	Status   string     `json:"status"`
	Info     string     `json:"info"`
	Count    string     `json:"count"`
	Geocodes []Geocode  `json:"geocodes"`
}

type Geocode struct {
	FormattedAddress string `json:"formatted_address"`
	Location         string `json:"location"`
}

// 配置常量
const (
	AmapAPIKey = "YOUR_AMAP_API_KEY" // 请替换为您的高德地图API Key
	AmapAPIURL = "https://restapi.amap.com/v3/geocode/geo"
	MaxRetries = 2
)

func main() {
	// 检查API Key
	if AmapAPIKey == "YOUR_AMAP_API_KEY" {
		fmt.Println("请先设置高德地图API Key")
		fmt.Println("申请地址: https://lbs.amap.com/dev/key/app")
		fmt.Println("然后将 AmapAPIKey 常量替换为您的实际API Key")
		return
	}

	// 文件路径
	inputFile := "/Users/renxing/code/interesting_toolbox/coordinates_map_v2/data3.json"
	outputFile := "/Users/renxing/code/interesting_toolbox/coordinates_map_v2/data3.json"

	// 读取JSON文件
	file, err := ioutil.ReadFile(inputFile)
	if err != nil {
		fmt.Printf("读取文件错误: %v\n", err)
		return
	}

	// 解析JSON数据
	var scenicSpots []ScenicSpot
	err = json.Unmarshal(file, &scenicSpots)
	if err != nil {
		fmt.Printf("解析JSON错误: %v\n", err)
		return
	}

	fmt.Printf("共找到 %d 个景区数据\n", len(scenicSpots))

	// 统计需要处理的数据
	var needProcess []*ScenicSpot
	for i := range scenicSpots {
		if scenicSpots[i].Location == "" {
			needProcess = append(needProcess, &scenicSpots[i])
		}
	}

	fmt.Printf("其中 %d 个需要获取坐标，%d 个已有坐标\n\n", len(needProcess), len(scenicSpots)-len(needProcess))

	if len(needProcess) == 0 {
		fmt.Println("所有景区都已包含坐标信息，无需处理")
		return
	}

	// 创建HTTP客户端
	client := &http.Client{
		Timeout: 60 * time.Second, // 增加超时时间
	}

	// 统计信息
	successCount := 0
	failCount := 0

	// 只处理没有坐标的数据
	for i, spot := range needProcess {
		fmt.Printf("处理第 %d/%d 个景区: %s\n", i+1, len(needProcess), spot.Name)

		// 检查是否有地址信息
		if spot.Detail == nil || spot.Detail.Address == "" {
			fmt.Printf("  ⚠️  缺少地址信息，跳过\n\n")
			failCount++
			continue
		}

		// 获取坐标
		location, err := getCoordinateWithRetry(client, spot)
		if err != nil {
			fmt.Printf("  ❌ 获取坐标失败: %v\n\n", err)
			failCount++

			// 如果是限流错误，直接退出
			if strings.Contains(err.Error(), "CUQPS_HAS_EXCEEDED_THE_LIMIT") ||
			   strings.Contains(err.Error(), "DAILY_QUERY_OVER_LIMIT") {
				fmt.Printf("  🚫 触发API限制，停止处理\n")
				break
			}
			continue
		}

		if location != "" {
			spot.Location = location
			fmt.Printf("  ✅ 坐标获取成功: %s\n\n", location)
			successCount++
		} else {
			fmt.Printf("  ❌ 未找到坐标信息\n\n")
			failCount++
		}

		// 大幅延长等待时间，避免触发QPS限制
		fmt.Printf("  ⏳ 等待3秒后继续...\n")
		time.Sleep(3 * time.Second)
	}

	// 保存更新后的JSON数据
	updatedData, err := json.MarshalIndent(scenicSpots, "", "  ")
	if err != nil {
		fmt.Printf("JSON序列化错误: %v\n", err)
		return
	}

	err = ioutil.WriteFile(outputFile, updatedData, 0644)
	if err != nil {
		fmt.Printf("写入文件错误: %v\n", err)
		return
	}

	// 输出统计信息
	fmt.Printf("\n处理完成！\n")
	fmt.Printf("本次成功获取坐标: %d 个\n", successCount)
	fmt.Printf("本次获取失败: %d 个\n", failCount)
	fmt.Printf("累计已有坐标: %d 个\n", len(scenicSpots)-len(needProcess)+successCount)
	fmt.Printf("仍缺少坐标: %d 个\n", len(needProcess)-successCount-failCount)
	fmt.Printf("更新后的数据已保存到: %s\n", outputFile)
}

// 带重试机制的坐标获取
func getCoordinateWithRetry(client *http.Client, spot *ScenicSpot) (string, error) {
	// 尝试多种地址组合策略
	addressVariants := generateAddressVariants(spot)

	for _, address := range addressVariants {
		fmt.Printf("  尝试地址: %s\n", address)

		for retry := 0; retry < MaxRetries; retry++ {
			location, err := getCoordinate(client, address)
			if err != nil {
				// 如果是限流错误，直接返回
				if strings.Contains(err.Error(), "CUQPS_HAS_EXCEEDED_THE_LIMIT") ||
				   strings.Contains(err.Error(), "DAILY_QUERY_OVER_LIMIT") {
					return "", err
				}

				if strings.Contains(err.Error(), "请求超过限制") {
					fmt.Printf("    ⚠️ 触发限流，等待5秒后重试\n")
					time.Sleep(5 * time.Second)
					continue
				}
				return "", err
			}

			if location != "" {
				return location, nil
			}

			if retry < MaxRetries-1 {
				fmt.Printf("    ⚠️ 未找到坐标，等待2秒后重试\n")
				time.Sleep(2 * time.Second)
			}
		}
	}

	return "", nil
}

// 生成多种地址变体以提高匹配成功率
func generateAddressVariants(spot *ScenicSpot) []string {
	var variants []string
	address := strings.TrimSpace(spot.Detail.Address)
	name := strings.TrimSpace(spot.Name)

	// 1. 详细地址 + 景区名称
	variants = append(variants, address+" "+name)

	// 2. 仅详细地址
	variants = append(variants, address)

	// 3. 清理地址中的冗余信息
	cleanedAddress := cleanAddress(address)
	if cleanedAddress != address {
		variants = append(variants, cleanedAddress+" "+name)
		variants = append(variants, cleanedAddress)
	}

	// 4. 区域 + 景区名称（作为备选）
	variants = append(variants, spot.Area+" "+name)

	// 5. 如果地址较短，直接使用名称
	if len(address) < 10 {
		variants = append(variants, name)
	}

	return variants
}

// 清理地址中的冗余信息
func cleanAddress(address string) string {
	// 移除常见的冗余词
	redundantWords := []string{
		"地址：", "地址:", "详细地址：", "详细地址:", "位置：", "位置:",
		"北京市", "天津市", "石家庄市", "保定市", "张家口市",
	}

	cleaned := address
	for _, word := range redundantWords {
		cleaned = strings.Replace(cleaned, word, "", -1)
	}

	// 移除多余空格
	cleaned = strings.TrimSpace(cleaned)
	cleaned = regexp.MustCompile(`\s+`).ReplaceAllString(cleaned, " ")

	return cleaned
}

// 获取坐标（基础函数）
func getCoordinate(client *http.Client, address string) (string, error) {
	params := url.Values{}
	params.Add("key", AmapAPIKey)
	params.Add("address", address)
	params.Add("output", "json")

	// 根据地址判断城市
	city := guessCityFromAddress(address)
	if city != "" {
		params.Add("city", city)
	}

	requestURL := AmapAPIURL + "?" + params.Encode()

	resp, err := client.Get(requestURL)
	if err != nil {
		return "", fmt.Errorf("请求失败: %v", err)
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("读取响应失败: %v", err)
	}

	var amapResp AmapResponse
	err = json.Unmarshal(body, &amapResp)
	if err != nil {
		return "", fmt.Errorf("解析响应失败: %v", err)
	}

	if amapResp.Status != "1" {
		return "", fmt.Errorf("API错误: %s", amapResp.Info)
	}

	if len(amapResp.Geocodes) == 0 {
		return "", nil
	}

	return amapResp.Geocodes[0].Location, nil
}

// 从地址中猜测城市
func guessCityFromAddress(address string) string {
	if strings.Contains(address, "北京") || strings.Contains(address, "朝阳") ||
	   strings.Contains(address, "海淀") || strings.Contains(address, "东城") ||
	   strings.Contains(address, "西城") || strings.Contains(address, "丰台") {
		return "北京"
	}
	if strings.Contains(address, "天津") {
		return "天津"
	}
	if strings.Contains(address, "石家庄") {
		return "石家庄"
	}
	if strings.Contains(address, "保定") {
		return "保定"
	}
	if strings.Contains(address, "张家口") {
		return "张家口"
	}
	return ""
}